package kernel;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;

import kernel.process.AbstractProcess;
import kernel.process.ProcessDispatcher;

/**
 * Class responsible for preserving running processes in the memory. Contains a table of processes (stores as process dispatchers).
 * @author Petr Hajek
 */
public class ProcessMemory {
	private static ArrayList<ProcessDispatcher> processList;
	private static ArrayBlockingQueue<List<AbstractProcess>> newProc;
	private static ArrayBlockingQueue<Long> oldProc;
	public static long dispCounter = 1;

	private static String programLoc = "program.";
	private static String[][] commands = {
		
							{"cat", programLoc + "Cat"},
							{"type", programLoc + "Cat"},
							{"sort", programLoc + "Sort"},
							{"cd", programLoc + "Cd"},
							{"chdir", programLoc + "Cd"},
							{"echo", programLoc + "Echo"},
							{"sort", programLoc + "Sort"},
							{"shell", programLoc + "Shell"},
							{"ls", programLoc + "Ls"},
							{"dir", programLoc + "Ls"},
							{"pwd", programLoc + "Pwd"},
							{"man", programLoc + "Man"},
							{"help", programLoc + "Man"},
							{"mkdir", programLoc + "Mkdir"},
							{"md", programLoc + "Mkdir"},
							{"ps", programLoc + "Ps"},
							{"kill", programLoc + "Kill"},
							{"touch", programLoc + "Touch"},
							//{"cp", programLoc + "Cp"},
							{"rm", programLoc + "Rm"},
							{"exit", programLoc + "Exit"},
							{"uniq", programLoc + "Uniq"},
							{"shutdown", programLoc + "Shutdown"},
							{"login", programLoc + "Login"}
							
	};

	/**
	 * Gets the existing commands user can use in the operating system.
	 * @return	Valid commands user can use.
	 */
	public static String[][] getCommands() {
		return commands;
	}
	
	/**
	 * Save a new processDispatcher into the process memory.
	 * @param process	New process dispatcher.
	 */
	public static void addProcess(ProcessDispatcher process) {
		synchronized(processList) {
			processList.add(process);
		}
	}
	
	/**
	 * Delete old processDispatcher from the process memory.
	 * @param id	ID of the process dispatcher to be deleted.
	 */
	public static void deleteProcess(long id) {
		synchronized(processList) {
			for (int i = 0; i < processList.size(); i++) {
				if (id == processList.get(i).getDispId()) {
					processList.remove(i);
				}
			}
		}
	}
	
	/**
	 * Creates the new process table.
	 */
	public static void createProcessList() {
		processList = new ArrayList<ProcessDispatcher>(10);
	}
	
	/**
	 * Creates the queue (shared memory between process manager and process creator) for new processes.
	 */
	public static void createNewProcessQueue() {
		newProc = new ArrayBlockingQueue<List<AbstractProcess>>(10);
	}
	
	/**
	 * Creates the queue (shared memory between process dispatcher and process destructor) for finished processes.
	 */
	public static void createOldProcessQueue() {
		oldProc = new ArrayBlockingQueue<Long>(10);
	}
	
	/**
	 * Puts a new list of commands into the shared queue.
	 * @param proc	List of commands stores as list of AbstractProcess structures.
	 * @throws InterruptedException
	 */
	public static void putIntoNewPQ(List<AbstractProcess> proc) throws InterruptedException {
		newProc.put(proc);
	}
	
	/**
	 * Take a new list of commands from the shared queue.
	 * @return		List of commands stores as list of AbstractProcess structures.
	 * @throws InterruptedException
	 */
	public static List<AbstractProcess> takeFromNewPQ() throws InterruptedException {
		return newProc.take();
	}
	
	/**
	 * Puts a finished dispatcher into the shared queue.
	 * @param id	ID of a finished process dispatcher.
	 * @throws InterruptedException
	 */
	public static void putIntoOldPQ(long id) throws InterruptedException {
		oldProc.put(id);
	}
	
	/**
	 * Take a finished dispatcher into the shared queue.
	 * @return	ID of a finished process dispatcher.
	 * @throws InterruptedException
	 */
	public static long takeFromOldPQ() throws InterruptedException {
		return oldProc.take();
	}
	
	/**
	 * Get all running processes of 1 specific user stored as string.
	 * @param userID	ID of the specific user.
	 * @return			Running processes of 1 specific user.
	 */
	public static String getRunningProgramsSingleUser(String userID) {
		StringBuilder str = new StringBuilder();
		String kernel = null;
		synchronized(processList) {
			for (int i = 0; i < processList.size(); i++) {
				if (processList.get(i).getDispId() != 0 && processList.get(i).getUserId().equals(userID)) 
					str.append(processList.get(i).getRunningProgramsInDisp());
				else {
					if (kernel == null) kernel = "root\t0\t?\t" + processList.get(i).getStartTime() + "\tkernel\n";
				}
			}
		}
		return kernel + str.toString();
	}
	
	/**
	 * Get all running processes of all users stored as string.
	 * @return		Running processes of 1 all users.
	 */
	public static String getRunningProgramsAll() {
		StringBuilder str = new StringBuilder();
		String kernel = null;
		synchronized(processList) {
			for (int i = 0; i < processList.size(); i++) {
				if (processList.get(i).getDispId() != 0) str.append(processList.get(i).getRunningProgramsInDisp());
				else {
					if (kernel == null) kernel = "root\t0\t?\t" + processList.get(i).getStartTime() + "\tkernel\n";
				}
			}
		}
		return kernel + str.toString();
	}
	
	/**
	 * Get all running processes.
	 * @return	All running processes.
	 */
	public static AbstractProcess[] getAllPrograms() {
		List<AbstractProcess> ap = new ArrayList<AbstractProcess>(0);
		synchronized(processList) {
			for (int i = 0; i < processList.size(); i++) {
				ap.addAll(processList.get(i).getPrograms());
			}
		}
		return ap.toArray(new AbstractProcess[ap.size()]);
	}
	
	/**
	 * Detects whether the kernel is running or not.
	 * @return	True if the kernel is running, false otherwise.
	 */
	public static boolean isKernelRunning() {
		synchronized(processList) {
			for (int i = 0; i < processList.size(); i++) {
				if (processList.get(i).getDispId() == 0) {
					return true;
				}
			}
			return false;
		}
	}
	
	/**
	 * Searches for a process with given id.
	 * @param id	ID of the searched process.
	 * @return		Found process with given ID. If the process was not found, returns null.
	 */
	public static AbstractProcess findProgramWithID(long id) {
		for (int i = 0; i < processList.size(); i++) {
			AbstractProcess ap = processList.get(i).containsProgram(id);
			if (ap != null) {
				return ap;
			}
		}
		return null;
	}

	/**
	 * Get ProcessDispatchers of the kernel running processes.
	 * @return	Process dispatchers of the kernel running processes.
	 */
	public static ProcessDispatcher[] getKernelPd() {
		int index = 0;
		ProcessDispatcher[] kernel = new ProcessDispatcher[2];
		for (int i = 0; i < processList.size(); i++) {
			if (processList.get(i).getDispId() == 0) {
				kernel[index++] = processList.get(i);
			}
		}
		return kernel;
		
	}
}
